//
// Created by lxinp on 2022/8/14.
//

#ifndef DATASTRUCTURE_RANDOM_H
#define DATASTRUCTURE_RANDOM_H

#include <cstdint>
#include <sys/time.h>

// Adapt from
// http://software.intel.com/en-us/articles/fast-random-number-generator-on-the-intel-pentiumr-4-processor/

namespace util {
    namespace internal {
        inline uint32_t time_seed() {
            timeval tv;
            gettimeofday(&tv, nullptr);
            return tv.tv_sec ^ tv.tv_usec;
        }

        inline int_fast16_t rand(uint32_t setseed) {
            static uint32_t seed = (uint32_t) (-1);
            if (setseed != (uint32_t) (-2)) {
                seed = setseed;
            }
            if (setseed == (uint32_t) (-1)) {
                seed = time_seed();
            }
            seed = 214013 * seed + 2531011;
            return (seed >> 16) & 0x7FFF;
        }
    }

    inline int_fast16_t urand() {
        return internal::rand(-2);
    }

    inline int_fast16_t urandn(int32_t n) {
        return urand() % n;
    }

    inline void usrand(uint64_t seed) {
        internal::rand(seed);
    }


    class Random {
    private:
        static uint32_t auto_seed(uint32_t set_seed);
        uint32_t seed;
    public:
        Random();
        Random(uint64_t s);

        int_fast16_t rand();
        int_fast16_t rand(int32_t n);
        int_fast16_t operator()();
        int_fast16_t operator()(int32_t n);
        void srand(uint64_t s);
    };


    inline uint32_t Random::auto_seed(uint32_t set_seed) {
        static uint32_t internal_seed = 0;
        if (set_seed == 0) return internal_seed;

        uint32_t result = internal_seed;
        internal_seed = set_seed;
        return result;
    }


    inline Random::Random() {
        if (auto_seed(0) == 0) {
            auto_seed(internal::time_seed());
        }
        seed = auto_seed(0);
        auto_seed((rand() << 16) ^ (rand()) ^ internal::time_seed());
    }


    inline Random::Random(uint64_t s) : seed(s) {}


    inline void Random::srand(uint64_t s) {
        seed = s;
    }


    inline int_fast16_t Random::rand() {
        seed = 214013 * seed + 2531011;
        return (seed >> 16) & 0x7FFF;
    }


    inline int_fast16_t Random::rand(int32_t n) {
        return rand() % n;
    }


    inline int_fast16_t Random::operator()() {
        return rand();
    }


    inline int_fast16_t Random::operator()(int32_t n) {
        return rand(n);
    }
}

#endif //DATASTRUCTURE_RANDOM_H
